﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Engine;
using Engine.Attributes;

namespace Engine.Components.CompArch
{
    

    public class RandomMemoryAccessorCpu: UserCodedComponent
    {
        /// <summary>
        /// Sets the address to be read or written
        /// </summary>
        public readonly OutPort<UInt32> MemoryAddressRegister = new OutPort<UInt32>();

        /// <summary>
        /// Register containing data to be written to memory
        /// </summary>
        public readonly OutPort<byte[]> MemoryWriteRegister = new OutPort<byte[]>();

        /// <summary>
        /// Register receiving data read from memory
        /// </summary>
        public readonly InPort<byte[]> MemoryReadRegister = new InPort<byte[]>();

        /// <summary>
        /// true if writing to memory, false otherwise
        /// </summary>
        public readonly OutPort<bool> RWFlag = new OutPort<bool>();

        /// <summary>
        /// This is set if there is a read/write error (usually an invalid address)
        /// </summary>
        public readonly InPort<bool> MemErrorFlag = new InPort<bool>();

        [Inspectable]
        public int SuccessfulWrites { get; private set; }

        [Inspectable]
        public int SuccessfulReads { get; private set; }

        private Random rng = new Random();
        private Thread executor;

        /// <summary>
        /// Default constructor (required for all Components)
        /// </summary>
        public RandomMemoryAccessorCpu()
        {
            executor = new Thread(new ParameterizedThreadStart(this.Run));
            SuccessfulReads = 0;
            SuccessfulWrites = 0;
            MemoryReadRegister.Run = this.MemoryReadRegisterHandler;
            
        }

        /// <summary>
        /// Starts this component executing
        /// </summary>
        public void Start()
        {
            this.Run(null);
        }

        /// <summary>
        /// Main execution loop for the thread
        /// </summary>
        /// <param name="obj">this parameter is unused; it is required for the ParameterizedThreadStart delegate type</param>
        private void Run(object obj)
        {
            for (int i = 0; i < 100; i++)
            {
                // generate a random address
                MemoryAddressRegister.Value = (UInt32)rng.Next(300);
                
                // randomly set as read or write
                bool isWrite = rng.NextBool();

                if (isWrite && !MemErrorFlag.Value)
                {
                    MemoryWriteRegister.Value = rng.NextBytes(4);
                    RWFlag.Value = true;
                    SuccessfulWrites++;
                }
                else
                {
                    RWFlag.Value = false; // initiate the read
                }
            }
        }

        /// <summary>
        /// Delegate for the MemoryReadRegister InPort
        /// </summary>
        private void MemoryReadRegisterHandler()
        {
            if (!this.MemErrorFlag.Value) this.SuccessfulReads++;
        }
    }

    public static class RandomExtension
    {
        /// <summary>
        /// Extension method to add a UInt32 generator to Random
        /// </summary>
        /// <param name="r"></param>
        /// <returns>a random UInt32</returns>
        public static UInt32 NextUInt32(this Random r)
        {
            byte[] buf = new byte[4];
            r.NextBytes(buf);
            return BitConverter.ToUInt32(buf, 0);
        }

        /// <summary>
        /// Extension method to add a bool generator to Random
        /// </summary>
        /// <param name="r">a random bool</param>
        /// <returns></returns>
        public static bool NextBool(this Random r)
        {
            return r.Next() % 2 == 0;
        }

        /// <summary>
        /// Easier-to-use version of NextBytes
        /// </summary>
        /// <param name="r"></param>
        /// <param name="numBytes">the number of bytes to generate</param>
        /// <returns>an array, of numBytes length, of random bytes</returns>
        public static byte[] NextBytes(this Random r, int numBytes)
        {
            byte[] buf = new byte[numBytes];
            r.NextBytes(buf);
            return buf;
        }
    }
}
